Skip to content

2700. 两个对象之间的差异

2700. 两个对象之间的差异

ts
type JSONValue =
  | null
  | boolean
  | number
  | string
  | JSONValue[]
  | { [key: string]: JSONValue }
type Obj = Record<string, JSONValue> | Array<JSONValue>

const isObject = (object: unknown) =>
  typeof object === 'object' && object !== null

function type(obj: unknown): string {
  return Object.prototype.toString.call(obj).slice(8, -1)
}

function objDiff(obj1: Obj, obj2: Obj): Obj {
  // 类型不同
  if (type(obj1) !== type(obj2)) return [obj1, obj2]
  // 基础类型比较
  if (!isObject(obj1)) return obj1 === obj2 ? {} : [obj1, obj2]
  // 过滤非共同子项
  const sameKeys = Object.keys(obj1).filter((key) => Object.hasOwn(obj2, key))

  const diff: Obj = {}
  // 遍历相同key
 for (const key of sameKeys) {
    const sub = objDiff((obj1 as any)[key], (obj2 as any)[key])
    if (Object.keys(sub).length > 0) {
      (diff as any)[key] = sub
    }
  }
  return diff
}

diff 的是什么

areDeeplyEqual 不同,objDiff 不是返回布尔值,而是返回一个差异对象,只保留不同的部分。

设计上的几个决策:

1. 类型不同 → 返回 [obj1, obj2] 原始值和最终值一起返回,调用方可以知道「从什么变成了什么」。

2. 基本类型相等 → 返回空对象 `{}``。 差异结果只记录不同的部分,相等的属性会被过滤掉。递归向上时,如果子对象的 diff 是空对象(即所有子属性都相等),这个 key 也不会出现在最终结果中。

3. 只比较共同 key。 如果 obj1 有而 obj2 没有(或反之)的 key,不纳入 diff 结果。这意味着这里返回的是“共同结构里的差异”,而不是完整增删改对比。

性能考虑

Object.keys(obj1).filter + Object.hasOwn(obj2, key) 对每个对象都调用一次,嵌套层级深时会有大量 key 枚举。如果对象键数量很大,考虑用 Set 优化。

另外,Object.prototype.toString.call(obj)typeof 更精确(能区分数组和对象),但每次调用都有函数调用和字符串切片开销。在递归中使用时可以缓存类型检查结果。